package retrofit2.spring.register;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import retrofit2.spring.annotation.EnableRetrofitClients;
import retrofit2.spring.annotation.RetrofitClient;
import retrofit2.spring.factory.RetrofitClientFactoryBean;

import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * RetrofitClientsRegister
 * <p>
 * 动态注册 Bean 到 Spring 上下文中
 *
 * @author archx
 * @link { https://www.jianshu.com/p/74e0fa334731 }
 * @since 2020/7/2 11:15
 */
@Slf4j
public class RetrofitClientsRegister implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, ResourceLoaderAware, EnvironmentAware {

    private ClassLoader classLoader;
    private ResourceLoader resourceLoader;
    private Environment environment;

    private Map<String, Object> enableYunGongBaoAnnotationAttributes;


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        enableYunGongBaoAnnotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableRetrofitClients.class.getName(), true);
        this.registerRetrofiltClient(importingClassMetadata, registry);
    }

    @SuppressWarnings("unchecked")
    private void registerRetrofiltClient(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获取Provider 其实就是一个是扫描器，提供扫描的功能
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        // 给扫描器设置资源加载器
        scanner.setResourceLoader(resourceLoader);

        String annotationStr = (String) enableYunGongBaoAnnotationAttributes.getOrDefault("annotation", "");

        Class<? extends Annotation> annotationClazz = RetrofitClient.class;
        if (StringUtils.hasText(annotationStr)) {
            try {
                Class<?> clazz = Class.forName(annotationStr, false, classLoader);
                if (Annotation.class.isAssignableFrom(clazz)) {

                    annotationClazz = (Class<? extends Annotation>) clazz;
                }
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("annotation class not found : " + annotationStr);
            }
        }

        // 添加扫描组件需要过滤的类型，这里我们需要扫描所有添加注解 @RetrofitClient 的class
        AnnotationTypeFilter typeFilter = new AnnotationTypeFilter(annotationClazz);
        // 扫描器
        scanner.addIncludeFilter(typeFilter);

        String[] scanPackages = (String[]) enableYunGongBaoAnnotationAttributes.get("scanPackages");
        // 如果没有配置则直接就不扫描了  方法直接返回即可
        if (scanPackages == null || scanPackages.length == 0) {
            log.info("@EnableRetrofitClients 中的 scanPackages 值为空");
            return;
        }

        for (String packages : scanPackages) {
            // 获取指定包路径下面所有添加注解的bean
            Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packages);
            for (BeanDefinition beanDefinition : beanDefinitions) {
                // 含有注解的bean
                if (beanDefinition instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
                    AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                    // 该注解只能添加在接口上
                    Assert.isTrue(annotationMetadata.isInterface(), "@" + annotationClazz.getName() + " 只能标记在接口上");

                    // 将扫描到的接口 根据代理工厂生成实例对象 并且将该实例对象注册到Spring的上下文中
                    this.registerRetrofiltClientBean(registry, annotationMetadata, Objects.requireNonNull(annotationMetadata.getAnnotationAttributes(annotationClazz.getCanonicalName())));

                }
            }
        }

    }

    /**
     * 将接口根据代理工厂生成实例对象，并且将该实例对象注册到Spring的上下文中
     */
    private void registerRetrofiltClientBean(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> annotationAttributes) {
        // 获取注解所在的类名
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(RetrofitClientFactoryBean.class);

        // 这里直接使用反射的方式 给通过代理工厂生成的实例对象进行赋值

        // 注意 这里annotationMetadata.getClassName() 是字符串类型的，而在代理工厂类中 resourceClass 是Class类型的
        // 按理说 赋值的话应该会报不合理参数的，但是这里运行没有问题，可能是Spring内部做了处理
        definition.addPropertyValue("resourceClass", annotationMetadata.getClassName());
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

        String clazzName = className.substring(className.lastIndexOf(".") + 1);
        String value = (String) annotationAttributes.get("value");
        String alias = StringUtils.hasText(value) ? value : this.lowerFirstCase(clazzName);

        // 向Spring的上下文中注册bean组件
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

    private String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                // 过滤掉不是注解的 bean
                if (beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation()) {
                    isCandidate = true;
                }
                return isCandidate;
            }
        };
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}
